﻿/*
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

using System.Xml;
using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;
using Daidalos.Exception;
using Daidalos.Management;

namespace Daidalos.Font
{

    public sealed class CharacterSet
    {

        #region Attributes
        /// <summary>
        ///     Keep track of the active character sets to
        ///     avoid multiple instances of the sets.
        /// </summary>
        private static System.Collections.Generic.Dictionary<string, CharacterSet> activeSets = new System.Collections.Generic.Dictionary<string, CharacterSet>();

        /// <summary>
        ///     The leap to perform when encountering an
        ///     invalid character.
        /// </summary>
        private int whiteSpaceLeap = 5;

        /// <summary>
        ///     Stores the character spacing on the X axis.
        /// </summary>
        private int spacingX = 1;

        /// <summary>
        ///     Stores the character spacing on the Y axis.
        /// </summary>
        private int spacingY = 1;

        /// <summary>
        ///     The width of the texture.
        /// </summary>
        private int textureWidth;

        /// <summary>
        ///     The height of the texture.
        /// </summary>
        private int textureHeight;

        /// <summary>
        ///     The texture with the font image.
        /// </summary>
        private Daidalos.Graphics.Texture texture = null;

        /// <summary>
        ///     The name of the font.
        /// </summary>
        private string name = "";

        /// <summary>
        ///     The size of the font.
        /// </summary>
        private int size = 0;

        /// <summary>
        ///     Is the font printed in bold letters?
        /// </summary>
        private bool bold = false;

        /// <summary>
        ///     Is the font printed in italics?
        /// </summary>
        private bool italic = false;

        /// <summary>
        ///     How far do the used coordinates stretch
        ///     over the font image?
        /// </summary>
        private float stretch = 100.0f;

        /// <summary>
        ///     The height of a line of text. Used to
        ///     create custom spacing between single
        ///     lines.
        /// </summary>
        private int lineHeight = 0;

        /// <summary>
        ///     TODO: find out what the fuck this does.
        /// </summary>
        private int baseHeight = 0;

        /// <summary>
        ///     An array that stores the character 
        ///     information.
        /// </summary>
        private Daidalos.Font.Character[] characters = null;
        #endregion

        #region Properties
        /// <summary>
        ///     Retrieve the character spacing on the
        ///     X axis.
        /// </summary>
        public int SpacingX
        {
            get
            {
                return spacingX;
            }
        }

        /// <summary>
        ///     Retrieve the character spacing on the
        ///     Y axis.
        /// </summary>
        public int SpacingY
        {
            get
            {
                return spacingY;
            }
        }

        /// <summary>
        ///     Implements accessors to the white space 
        ///     leaping distance.
        /// </summary>
        public int WhiteSpaceLeap
        {
            get
            {
                return whiteSpaceLeap;
            }
            set
            {
                whiteSpaceLeap = (value > 0) ? value : 0;
            }
        }

        /// <summary>
        ///     Implements a selector for the texture's height.
        /// </summary>
        public int TextureHeight
        {
            get
            {
                return textureHeight;
            }
        }

        /// <summary>
        ///     Returns the name of the character set.
        /// </summary>
        public string Name
        {
            get
            {
                return name;
            }
        }

        /// <summary>
        ///     Implements a selector for the texture's width.
        /// </summary>
        public int TextureWidth
        {
            get
            {
                return textureWidth;
            }
        }
        /// <summary>
        ///     Implements a selector for the set's characters.
        /// </summary>
        public Daidalos.Font.Character[] Characters
        {
            get
            {
                return characters;
            }
        }

        /// <summary>
        ///     Implements a selector for the font's texture.
        /// </summary>
        public Daidalos.Graphics.Texture Texture
        {
            get
            {
                return texture;
            }
        }
        #endregion

        #region Constructors
        private CharacterSet(string name)
        {
            // Validate source.
            if (name == null || name == "")
            {
                throw new GenericException("Passed invalid character-set source!");
            }

			// Try to load the set.
            LoadFile(name);
        }
        #endregion

        #region Methods
        public static CharacterSet GetSetByName(string name)
        {
            // Validate the name.
            if (name == null || name == "")
            {
                throw new GenericException("Passed invalid character-set source!");
            }

			// Fetch a handle for the set.
			CharacterSet handle = null;
			
            // Check if the character set is stored in the dictionary.
			if (activeSets.ContainsKey(name))
			{
				return activeSets[name];
			}
			else
			{
				// Load the set from scratch.
				handle = new CharacterSet(name);

				// Did the loading succeed?
				if (handle != null)
				{
					// Store the handle.
					activeSets[name] = handle;
				}
			}		  
			return handle;
        }

        /// <summary>
        ///     This method loads a font with a certain name.
        /// </summary>
        /// <param name="file">
        ///     The name of the font. The font texture file
        ///     must be 'name.png' and the meta fiel must be
        ///     'name.xml'!
        /// </param>
        private void LoadFile(string name)
        {
            // Log the venture.
            LogManager.Instance.Log("Loading font \"" + name + "\".");
			
            // Try to load the font texture.
            LoadTexture(name + ".png");
            if (texture == null)
            {
                throw new GenericException( "Failed to load the font texture \"" + name + ".png\"!" );
            }			

            // Try to load the meta data.
            LoadMeta(@"fonts\" + name + ".xml");
        }

        /// <summary>
        ///     Load a texture-file into the font's texture.
        /// </summary>
        /// <param name="name">
        ///     The name of the texture.
        /// </param>
        private void LoadTexture(string name)
        {
			// Fetch the texture from the manager.
			texture = Management.ResourceManager.Instance.GetTextureByName(name);
        }

        /// <summary>
        ///     Load a XML description file.
        /// </summary>
        /// <param name="meta">
        ///     Path to the meta file.
        /// </param>
        private void LoadMeta(string meta)
        {
            // Create an XmlDocument object.
            XmlDocument xml = new XmlDataDocument();
            if (xml == null)
            {
                throw new GenericException("Failed to create an XmlDocument-object!");
            }

            // Load the meta file into the object.
            xml.Load(meta);
            
            // Extract the root element.
            XmlElement root = xml.DocumentElement;

            // Verify the presence of child nodes.
            if (root == null || !root.HasChildNodes)
            {
                throw new GenericException("Incomplete or damaged meta file!");
            }

            // Read info tag.
            XmlNode info = root.SelectSingleNode("info");
            if (info == null)
            {
                throw new GenericException("Missing info-tag in meta file!");
            }

            // Store parameters in attributes.
            if (info.Attributes["face"] == null)
            {
                throw new GenericException("Info tag lacks name information!");
            }
            name = info.Attributes["face"].Value;

            if (info.Attributes["size"] == null)
            {
                throw new GenericException("Info tag lacks size information!");
            }
            size = int.Parse(info.Attributes["size"].Value);

            if (info.Attributes["bold"] != null)
            {
                bold = int.Parse(info.Attributes["bold"].Value) == 0 ? false : true;
            }

            if (info.Attributes["italic"] != null)
            {
                italic = int.Parse(info.Attributes["italic"].Value) == 0 ? false : true;
            }

            if (info.Attributes["stretchH"] != null)
            {
                stretch = float.Parse(info.Attributes["stretchH"].Value);
            }

            // Extract the spacing
            if (info.Attributes["spacing"] == null)
            {
                throw new GenericException("Info tag lacks spacing information!");
            }
            string[] spacings = info.Attributes["spacing"].Value.Split(new char[] { ',' });
            if (spacings.Length == 2)
            {
                int.Parse(spacings[0]);
                int.Parse(spacings[1]);
            }
            else
            {
                throw new GenericException("Info tag contains malformated spacing information.");
            }

            // Retrieve common image information.
            XmlNode common = root.SelectSingleNode("common");
            if (common == null)
            {
                throw new GenericException("Missing common-tag in meta file!");
            }

            if (common.Attributes["lineHeight"] == null)
            {
                throw new GenericException("Common tag lacks line-height information!");
            }
            lineHeight = int.Parse(common.Attributes["lineHeight"].Value);

            if (common.Attributes["base"] == null)
            {
                throw new GenericException("Common tag lacks base-height information!");
            }
            baseHeight = int.Parse(common.Attributes["base"].Value);

            if (common.Attributes["scaleW"] == null)
            {
                throw new GenericException("Common tag lacks scale width information!");
            }
            textureWidth = int.Parse(common.Attributes["scaleW"].Value);

            if (common.Attributes["scaleH"] == null)
            {
                throw new GenericException("Common tag lacks scale height information!");
            }
            textureHeight = int.Parse(common.Attributes["scaleH"].Value);

            // Retrieve character information
            XmlNode chars = root.SelectSingleNode("chars");
            if (chars == null)
            {
                throw new GenericException("Missing characters-tag in meta file!");
            }

            if (chars.Attributes["count"] == null)
            {
                throw new GenericException("Characters tag lacks count information!");
            }
            int count = int.Parse(chars.Attributes["count"].Value);

            // Extract all character data.
            XmlNodeList charList = chars.SelectNodes("char");
            if (charList != null)
            {
                // Determine the maximum character Id used by the font.
                int maxId = 0;
                foreach (XmlNode node in charList)
                {
                    if (node.Attributes["id"] == null)
                        continue;
                    int currentId = int.Parse(node.Attributes["id"].Value);
                    maxId = (currentId > maxId) ? currentId : maxId;
                }

                // Allocate characters
                characters = new Daidalos.Font.Character[maxId + 1];
                if (characters == null)
                {
                    throw new GenericException("Character allocation failed!");
                }
                for (int i = 0; i < maxId + 1; ++i)
                {
                    characters[i] = null;
                }

                // Extract characters.
                foreach (XmlNode node in charList)
                {
                    // Check if all required attributes are present.
                    if (node.Attributes["id"] != null && node.Attributes["x"] != null && node.Attributes["y"] != null &&
                        node.Attributes["width"] != null && node.Attributes["height"] != null && node.Attributes["xoffset"] != null &&
                        node.Attributes["yoffset"] != null && node.Attributes["xadvance"] != null)
                    {
                        // Retrieve the character ID.
                        int id = int.Parse(node.Attributes["id"].Value);
                        characters[id] = new Daidalos.Font.Character();

                        // Setup character.
                        characters[id].X = int.Parse(node.Attributes["x"].Value);
                        characters[id].Y = int.Parse(node.Attributes["y"].Value);
                        characters[id].Width = int.Parse(node.Attributes["width"].Value);
                        characters[id].Height = int.Parse(node.Attributes["height"].Value);
                        characters[id].XOffset = int.Parse(node.Attributes["xoffset"].Value);
                        characters[id].YOffset = int.Parse(node.Attributes["yoffset"].Value);
                        characters[id].Leap = int.Parse(node.Attributes["xadvance"].Value);
                    }
                }
            }
        }
        #endregion

    }

}
